package Operator;

import Billing.Billing;
import Client.Client;
import environment.IO;

import java.util.*;

public class Operator 
{
	private String name;
	
	protected HashMap<String, ClientInfo> clients;
	protected LinkedList<PriceList> rates;
	protected Queue<Integer> clientHistory;
	protected Stack<Double> satisfactionHistory;
	
	protected int totalUsers = 0;	/// users on the market
	protected int marketPosition = 0;	/// based on no. of users which operator are we
	protected double marketShare;	/// our users / totalUsers
	
	protected int HISTORY_SIZE = 12 * 10; /// how many months to remember
	protected int STRATEGY_EVALUATION_PERIOD = 3;	/// how often to think about changing strategy
	protected int SATISFACTORY_MARKET_POSITION = 2; /// when we think about ourselves as a leader
	
	protected Strategy myStrategy;
	
	public String getLastStrategyUsed()
	{
		return myStrategy.lastStrategy; 
	}
	
	public Operator(String name, Strategy strat)
	{
		this.myStrategy = strat;
		this.name = name;
		rates = new LinkedList<PriceList>();
		clients = new HashMap<String, ClientInfo>();
		
		clientHistory = new LinkedList<Integer>();
		satisfactionHistory = new Stack<Double>();
	}
	
	/**
	 * How many clients does this operator currently have.
	 * @return Number of clients 
	 */
	public int getClientCount()
	{
		return clients.size();
	}
	
	public double [] getPrices()
	{
		return rates.peek().getPrices();
	}
	
	public Double countPrice(Billing biling)
	{
		if (rates != null)
		{
			double min = Double.MAX_VALUE;
			PriceList minRate = rates.getFirst();
			
			// Return the lowest price across current rates
			for (Iterator<PriceList> iterator = rates.iterator(); iterator.hasNext();) 
			{
				PriceList rate = (PriceList) iterator.next();
				
				double val = rate.computeCost(biling);
				if (val < min)
				{
					min = val;
					minRate = rate;
				}
			}
			
			minRate.priceListUsers++;
			
			return min;
		}
		else
			return 0.0;
	}
	
	public void addClient(Client client)
	{
		clients.put(client.getName(), new ClientInfo(client.getName()));
	}
	
	public void removeClient(Client client)
	{
		clients.remove(client.getName());
	}

	public String getName()
	{
		return name;
	}
	
	public final void endPeriod(int totalUsers, int marketPosition, double [] bestPrices)
	{
		if (totalUsers == 0)
			return;
		
		this.totalUsers = totalUsers;
		this.marketPosition = marketPosition;
		
		// save current client count
		clientHistory.add(clients.size());
		
		if (clientHistory.size() > HISTORY_SIZE)
			clientHistory.poll();
		
		double outcome = evaluatePeriod();
		
		if( lowerPricesTrigger( outcome ) )
			lowerPrices(outcome, bestPrices);
		else if (higherPricesTrigger ( outcome ))
			higherPrices(outcome, bestPrices);
		
		// clean priceList usage statistics
		for (PriceList pr : rates)
		{
			pr.priceListUsers = 0;
		}
	}

	protected boolean higherPricesTrigger( double outcome )
	{
		return ( outcome >= 0 ) && 
				(marketPosition <= SATISFACTORY_MARKET_POSITION) && 
				satisfactionHistory.size() > STRATEGY_EVALUATION_PERIOD;		
	}
	
	
	protected boolean lowerPricesTrigger( double outcome )
	{
		return ( outcome <= 0 ) && 
				(marketPosition > SATISFACTORY_MARKET_POSITION) && 
				satisfactionHistory.size() > STRATEGY_EVALUATION_PERIOD;		
	}
	
	protected void lowerPrices( double outcome, double [] bestPrices )
	{
		IO.operatorPrint(name + ": złe wyniki, potrzebna zmiana strategii biznesowej!", IO.IMPORTANT);
		
		// keep only 3 rates
		if (rates.size() > 2)
		{
			Collections.sort(rates, new Comparator<Object>() {
				public int compare(Object left, Object right) {
					Integer leftKey = ((PriceList) left).priceListUsers;
					Integer rightKey = ((PriceList) right).priceListUsers;

					return leftKey.compareTo(rightKey);
				}
			});
			
			rates.remove();
		}
		
		rates.add( myStrategy.lowerPrices(rates.peek(), bestPrices) );
	}
	
	protected void higherPrices( double outcome, double [] bestPrices )
	{
		IO.operatorPrint(name + ": dobre wyniki, możemy podnieść stawki.", IO.IMPORTANT);
		
		// keep only 3 rates
		if (rates.size() > 2)
		{	
			rates.remove();
		}
		
		rates.add( myStrategy.higherPrices(rates.peek(), bestPrices) );
	}

	/**
	 * Evaluates the last period based on history of number of users
	 * and results from previous months.
	 * 
	 * @param totalUsers How many users are there on the market
	 * @return This months result minus last months result
	 */
	protected double evaluatePeriod()
	{	
		double satisfaction = 0.0;	/// how happy are we with the result
		double outcome = 0.0; /// how do we do compared to last month
		
		marketShare = ((double) clientHistory.size() ) / ((double) totalUsers);
		
		satisfaction = marketShare;
		satisfactionHistory.push(satisfaction);
		
		if (satisfactionHistory.size() > 1)
			outcome = satisfactionHistory.peek() - satisfaction;
		else 
			outcome = satisfaction;
		
		return outcome;
	}
	
	public void addPriceList(PriceList list)
	{
		if (!rates.contains(list))
			rates.push(list); 
	}	
}
